package nl.unimaas.games.tron.engine;

import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;

import nl.unimaas.games.tron.player.FillPlayer;


/** Evaluates a position. Gives a positive score if the position is in favor of player 1 */
@SuppressWarnings("serial")
public class Evaluator implements Serializable {
	
	private final Board board;
	private final EvaluationFunction[] evals;
	private final double[] scores;
	private final double[] weights;
	private final Object[] states;
	private double score = 0;

	public Evaluator(Board board, EvaluationFunction[] evals, double[] weights) {
		this.board = board;
		this.evals = evals;
		this.states = new Object[evals.length];
		this.scores = new double[evals.length];
		this.weights = weights;
	}
	
	private Evaluator(Board board, EvaluationFunction[] evals, double[] weights, double[] scores, double score, Object[] states) {
		this.board = board;
		this.evals = evals;
		this.states = states;
		this.scores = scores;
		this.weights = weights;
	}
	
	/** Re-computes the evaluation value from the start */
	public void compute(int playerNr) {
		score = 0;
		for (int i = 0; i < evals.length; i++) {
			double s = evals[i].compute(board, playerNr);
			scores[i] = s;
			score += s * weights[i];
		}
	}
	
	public double getScore() {
		return score;
	}
	
	public static interface EvaluationFunction extends Serializable {
		public double compute(Board board, int playerNr);
	}
	
	public static interface ConnectionEvaluationFunction extends EvaluationFunction {
		public boolean isConnected();
	}
	
	public static interface SpaceEvaluationFunction extends EvaluationFunction {
		public int[] getLowerBound();
		public int[] getUpperBound();
	}
	
	public final static EvaluationFunction FREE_SPACE = new EvaluationFunction() {
		@Override
		public double compute(Board board, int playerNr) {
			int freespace = 0;
			for (int x = 0; x < board.getWidth(); x++) {
				for (int y = 0; y < board.getHeight(); y++) {
					if (board.isEmpty(x, y))
						freespace++;
				}
			}
			return freespace;
		}
	};
	
	public static class SpaceEvaluator implements ConnectionEvaluationFunction, SpaceEvaluationFunction {
		private final static byte COLOR1 = -2, COLOR2 = -3;
		private final static byte EMPTY = Board.EMPTY_FIELD;
		private final FillPlayer filler = new FillPlayer("", 0, Color.WHITE);
		
		private int[] lower, upper;
		
		private boolean connected = false;
		
		@Override
		public double compute(Board board, int playerNr) {
			upper = computeUpperSpace(board);
			if (connected) {
				lower = new int[] {0, 0};
				return Double.NaN;
			}
			lower = computeLowerSpace(board);
			
			double diff;
			if (upper[1] < lower[0]) //P1 has more space
				diff = lower[0] - upper[1];
			else if (upper[0] < lower[1]) //P2 has more space
				diff = -lower[1] + upper[0];
			else
				diff = 0;
			
			if (playerNr != 0)
				diff *= -1;
			return diff;
		}
		
		private int[] computeLowerSpace(Board board) {
			Board[] boards = new Board[] {board.deepClone(false), board.deepClone(false)};
			int[] lowers = new int[2];
			Board b;
			for (int i = 0; i < 2; i++) {
				b = boards[i];
				filler.setNumber(i);
				int m, moves = 0;
				while (true) {
					m = filler.getMove(b, 0);
					if (m != Board.MOVE_NONE) {
						b.performMove(i, m);
						moves++;
					}
					else
						break;
				}
				lowers[i] = moves;
			}
			return lowers;
		}
		
		private int[] computeUpperSpace(Board board) {
			byte[][] colorBoard1 = board.getClonedBoardArray();
			byte[][] colorBoard2 = board.getClonedBoardArray();
			
			int pos = board.getPlayerPosition(0);
			int x1 = Board.posToX(pos);
			int y1 = Board.posToY(pos);
			int pos2 = board.getPlayerPosition(1);
			int x2 = Board.posToX(pos2);
			int y2 = Board.posToY(pos2);
			int[][] playerPos = new int[][] {{x1, y1},{x2, y2}};
			connected = false;

			int[] colorCounts1 = new int[2], colorCounts2 = new int[2];
			//players start on COLOR1
			step(colorBoard1, playerPos[0][0], playerPos[0][1], COLOR1, colorCounts1);

			if (Board.hasAdjacentFieldSmallerThan(colorBoard1, x2, y2, EMPTY)) {
				//connected!
				connected = true;
				colorBoard2 = Board.cloneArray(colorBoard1);
				colorCounts2[0] = colorCounts1[0];
				colorCounts2[1] = colorCounts1[1];
			}
			step(colorBoard2, playerPos[1][0], playerPos[1][1], COLOR1, colorCounts2);

			int total1 = colorCounts1[0] + colorCounts1[1] - 1;
			int total2 = colorCounts2[0] + colorCounts2[1] - 1;
			int moves1 = total1 - Math.abs(colorCounts1[0] - colorCounts1[1]);
			int moves2 = total2 - Math.abs(colorCounts2[0] - colorCounts2[1]);
			return new int[] {moves1, moves2};
		}

		@Override
		public boolean isConnected() {
			return connected;
		}

		@Override
		public int[] getLowerBound() {
			return lower;
		}

		@Override
		public int[] getUpperBound() {
			return upper;
		}
		
		private void step(byte[][] board, int x, int y, byte color, int[] colorCount) {
			if (board[x][y] == EMPTY)
				board[x][y] = color;
			
			if (color == COLOR1) {
				colorCount[0]++;
				color = COLOR2;
			}
			else {
				colorCount[1]++;
				color = COLOR1;
			}

			if (board[x - 1][y] == EMPTY)
				step(board, x - 1, y, color, colorCount);
			if (board[x + 1][y] == EMPTY)
				step(board, x + 1, y, color, colorCount);
			if (board[x][y - 1] == EMPTY)
				step(board, x, y - 1, color, colorCount);
			if (board[x][y + 1] == EMPTY)
				step(board, x, y + 1, color, colorCount);
		}
	}
	
	public final static VoronoiSpaceEvaluator VORONOI_SPACE = new VoronoiSpaceEvaluator();
	
	public static class VoronoiSpaceEvaluator implements SpaceEvaluationFunction {
		private final static short EMPTY = 0;
		private final static short WALL = Short.MAX_VALUE;
		private final static short ENCOUNTER = 1000;
		private final static short START1 = 1, START2 = -1;
		private final static int COM = 8192;
		private final static int COM_PLUS = 256;
		
		@Override
		public double compute(Board board, int playerNr) {
			byte[][] b = board.board;
			int w = b.length;
			int h = b[0].length;
			byte[] tmpY;
			short[][] fillBoard = new short[w][];
			short[] fillY;
			for (int x = w; --x >= 0;) {
				tmpY = b[x];
				fillY = new short[h];
				for (int y = h; --y >= 0;) {
					if (tmpY[y] != Board.EMPTY_FIELD)
						fillY[y] = WALL;
				}
				fillBoard[x] = fillY;
			}
			
			int p1Space = 0, p2Space = 0;
			ArrayDeque<Integer> posQ = new ArrayDeque<Integer>(32);
			
			addPosition(fillBoard, posQ, board.getPlayerPosition(0), START1);
			addPosition(fillBoard, posQ, board.getPlayerPosition(1), START2);
			int p, x, y;
			int num, newNum;
			while (!posQ.isEmpty()) {
				p = posQ.poll();
				num = p / COM - COM_PLUS;
				p = p % COM;
				x = Board.posToX(p);
				y = Board.posToY(p);
				if (num > 0)
					newNum = num + 1;
				else
					newNum = num - 1;
				
				int space = 0;
				
				//left
				if (fillBoard[x-1][y] == EMPTY) {
					addPosition(fillBoard, posQ, x-1, y, newNum);
					space++;
				}
				else {
					if (num < 0 && newNum + fillBoard[x-1][y] == 0) {
						space++;
						fillBoard[x-1][y] = ENCOUNTER;
					}
				}
				
				//right
				if (fillBoard[x+1][y] == EMPTY) {
					addPosition(fillBoard, posQ, x+1, y, newNum);
					space++;
				}
				else {
					if (num < 0 && newNum + fillBoard[x+1][y] == 0) {
						space++;
						fillBoard[x+1][y] = ENCOUNTER;
					}
				}
				
				//up
				if (fillBoard[x][y-1] == EMPTY) {
					addPosition(fillBoard, posQ, x, y-1, newNum);
					space++;
				}
				else {
					if (num < 0 && newNum + fillBoard[x][y-1] == 0) {
						space++;
						fillBoard[x][y-1] = ENCOUNTER;
					}
				}
				
				//down
				if (fillBoard[x][y+1] == EMPTY) {
					addPosition(fillBoard, posQ, x, y+1, newNum);
					space++;
				}
				else {
					if (num < 0 && newNum + fillBoard[x][y+1] == 0) {
						space++;
						fillBoard[x][y+1] = ENCOUNTER;
					}
				}
				
				if (num > 0)
					p1Space += space;
				else
					p2Space += space;
			}
			//Board.printBoardArray(fillBoard);
			//System.out.println("P1: " + p1Space + "  P2: " + p2Space + " P=" + playerNr);
			if (playerNr == 3) {
				Board.printBoardArray(fillBoard);
				System.out.println("P1: " + p1Space + "  P2: " + p2Space);
			}
			if (playerNr == 0)
				return p1Space - p2Space;
			else
				return p2Space - p1Space;
		}

		private static void addPosition(short[][] fillBoard, ArrayDeque<Integer> posQ, int x, int y, int num) {
			fillBoard[x][y] = (short) num;
			posQ.add((num + COM_PLUS) * COM + Board.xyToPos(x, y));
		}
		
		private static void addPosition(short[][] fillBoard, ArrayDeque<Integer> posQ, int pos, int num) {
			fillBoard[Board.posToX(pos)][Board.posToY(pos)] = (short) num;
			posQ.add((num + COM_PLUS) * COM + pos);
		}

		@Override
		public int[] getLowerBound() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public int[] getUpperBound() {
			// TODO Auto-generated method stub
			return null;
		}
	}
	
	
	
	@Override
	public Evaluator clone() {
		return new Evaluator(board, evals, weights.clone(), scores.clone(), score, states);
	}
	
	public Evaluator clone(Board newBoard) {
		return new Evaluator(newBoard, evals, weights.clone(), scores.clone(), score, states);
	}
}
